/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, Thomas J. Black
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package hudson.model;

import hudson.BulkChange;
import hudson.DescriptorExtensionList;
import hudson.Util;
import hudson.XmlFile;
import hudson.logging.HudsonTestLog;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.node_monitors.NodeMonitor;
import hudson.slaves.NodeDescriptor;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.json.JSONObject;

/**
 * Serves as the top of {@link Computer}s in the URL hierarchy.
 * <p>
 * Getter methods are prefixed with '_' to avoid collision with computer names.
 *
 * @author Kohsuke Kawaguchi
 */
@ExportedBean
public final class ComputerSet extends AbstractModelObject {
    /**
     * This is the owner that persists {@link #monitors}.
     */
    private static final Saveable MONITORS_OWNER = new Saveable() {
        public void save() throws IOException {
            getConfigFile().write(monitors);
            SaveableListener.fireOnChange(this, getConfigFile());
        }
    };

    private static final DescribableList<NodeMonitor,Descriptor<NodeMonitor>> monitors
            = new DescribableList<NodeMonitor, Descriptor<NodeMonitor>>(MONITORS_OWNER);

    @Exported
    public String getDisplayName() {
        return Messages.ComputerSet_DisplayName();
    }

    /**
     * @deprecated as of 1.301
     *      Use {@link #getMonitors()}.
     */
    public static List<NodeMonitor> get_monitors() {
        return monitors.toList();
    }

    @Exported(name="computer",inline=true)
    public Computer[] get_all() {
        return Hudson.getInstance().getComputers();
    }

    /**
     * Exposing {@link NodeMonitor#all()} for Jelly binding.
     */
    public DescriptorExtensionList<NodeMonitor,Descriptor<NodeMonitor>> getNodeMonitorDescriptors() {
        return NodeMonitor.all();
    }

    public static DescribableList<NodeMonitor,Descriptor<NodeMonitor>> getMonitors(){
        //resolve a bug find 2012/8/30 , the architectrue is repeat many times
        Set st = new HashSet(monitors.toList());
        monitors.clear();
        try {
            monitors.addAll(st);
        } catch (IOException ex) {
            Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " 处理architecture异常"+ex.getMessage());
        }
        return monitors;
    }

    /**
     * Returns a subset pf {@link #getMonitors()} that are {@linkplain NodeMonitor#isIgnored() not ignored}.
     */
    public static Map<Descriptor<NodeMonitor>,NodeMonitor> getNonIgnoredMonitors() {
        Map<Descriptor<NodeMonitor>,NodeMonitor> r = new HashMap<Descriptor<NodeMonitor>, NodeMonitor>();
        for (NodeMonitor m : monitors) {
            if(!m.isIgnored())
                r.put(m.getDescriptor(),m);
        }
        Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " now the NodeMonitor map "+r.size()+" "+r.keySet()+" "+r.values());
        return r;
    }

    /**
     * Gets all the slave names.
     */
    public List<String> get_slaveNames() {
        return new AbstractList<String>() {
            final List<Node> nodes = Hudson.getInstance().getNodes();

            public String get(int index) {
                return nodes.get(index).getNodeName();
            }

            public int size() {
                return nodes.size();
            }
        };
    }

    /**
     * Number of total {@link Executor}s that belong to this label that are functioning.
     * <p>
     * This excludes executors that belong to offline nodes.
     */
    @Exported
    public int getTotalExecutors() {
        int r=0;
        for (Computer c : get_all()) {
            if(c.isOnline())
                r += c.countExecutors();
        }
        return r;
    }

    /**
     * Number of busy {@link Executor}s that are carrying out some work right now.
     */
    @Exported
    public int getBusyExecutors() {
        int r=0;
        for (Computer c : get_all()) {
            if(c.isOnline())
                r += c.countBusy();
        }
        return r;
    }

    /**
     * {@code getTotalExecutors()-getBusyExecutors()}, plus executors that are being brought online.
     */
    public int getIdleExecutors() {
        int r=0;
        for (Computer c : get_all())
            if(c.isOnline() || c.isConnecting())
                r += c.countIdle();
        return r;
    }

    public String getSearchUrl() {
        return "/computers/";
    }

    public Computer getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
        return Hudson.getInstance().getComputer(token);
    }
    
    
    /**
     *check current all slave ,if support launch ,then connect is true 
     */
    public void do_launchAll(StaplerRequest req, StaplerResponse rsp) throws IOException {
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

        for(Computer c : get_all()) {
            if(c.isLaunchSupported())
                c.connect(true);
        }
        rsp.sendRedirect(".");
    }

    /**
     * Triggers the schedule update now.
     *
     * TODO: ajax on the client side to wait until the update completion might be nice.
     */
    public void doUpdateNow( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
        
        for (NodeMonitor nodeMonitor : NodeMonitor.getAll()) {
            Thread t = nodeMonitor.triggerUpdate();
            t.setName(nodeMonitor.getColumnCaption());
        }
        rsp.forwardToPreviousPage(req);
    }

    /**
     * First check point in creating a new slave.
     */
    public synchronized void doCreateItem( StaplerRequest req, StaplerResponse rsp,
                                           @QueryParameter String name, @QueryParameter String mode,
                                           @QueryParameter String from ) throws IOException, ServletException {
        final Hudson app = Hudson.getInstance();
        app.checkPermission(Hudson.ADMINISTER);  // TODO: new permission?

        if(mode!=null && mode.equals("copy")) {
            name = checkName(name);

            Node src = app.getNode(from);
            if(src==null) {
                rsp.setStatus(SC_BAD_REQUEST);
                if(Util.fixEmpty(from)==null)
                    sendError(Messages.ComputerSet_SpecifySlaveToCopy(),req,rsp);
                else
                    sendError(Messages.ComputerSet_NoSuchSlave(from),req,rsp);
                return;
            }

            // copy through XStream
            String xml = Hudson.XSTREAM.toXML(src);
            Node result = (Node)Hudson.XSTREAM.fromXML(xml);
            result.setNodeName(name);
            result.holdOffLaunchUntilSave = true;
               
            app.addNode(result);

            // send the browser to the config page
            rsp.sendRedirect2(result.getNodeName()+"/configure");
        } else {
            // proceed to step 2
            if(mode==null) {
                rsp.sendError(SC_BAD_REQUEST);
                return;
            }
            
            NodeDescriptor d = NodeDescriptor.all().find(mode);
            d.handleNewNodePage(this,name,req,rsp);
        }
    }

    /**
     * Really creates a new slave.
     * @since
     *   2012-03-03
     * add slaveCreator ,slaveManagersEmail,slaveip
     */
    public synchronized void doDoCreateItem( StaplerRequest req, StaplerResponse rsp,
                                           @QueryParameter String name,
                                           @QueryParameter String type ) throws IOException, ServletException, FormException {
        final Hudson app = Hudson.getInstance();
        app.checkPermission(Hudson.ADMINISTER);  // TODO: new permission?
        checkName(name);

        Node result = NodeDescriptor.all().find(type).newInstance(req, req.getSubmittedForm());
        app.addNode(result);

        // take the user back to the slave list top page
        rsp.sendRedirect2(".");
    }

    /**
     * Makes sure that the given name is good as a slave name.
     * @return trimmed name if valid; throws ParseException if not
     */
    public String checkName(String name) throws Failure {
        if(name==null)
            throw new Failure("Query parameter 'name' is required");

        //should filter chinese node name
        String pattern="[\u4e00-\u9fa5]+";  
        Pattern p=Pattern.compile(pattern);  
        Matcher result=p.matcher(name);
        if(result.find()){
            throw new Failure(Messages.ComputerSet_UseIpOrComputerName());
        }
        
        name = name.trim();
        Hudson.checkGoodName(name);

        if(Hudson.getInstance().getNode(name)!=null)
            throw new Failure(Messages.ComputerSet_SlaveAlreadyExists(name));

        // looks good
        return name;
    }

    /**
     * Makes sure that the given name is good as a slave name.
     */
    public FormValidation doCheckName(@QueryParameter String value) throws IOException, ServletException {
        Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

        if(Util.fixEmpty(value)==null)
            return FormValidation.ok();
        
        try {
            checkName(value);
            return FormValidation.ok();
        } catch (Failure e) {
            return FormValidation.error(e.getMessage());
        }
    }
    
    /**
     * nodes labels json api
     * @since 2012/8/16
     */
    public void doLabelsAndDescriptions(StaplerRequest req, StaplerResponse rsp)throws IOException{
        Set<Label> set= Hudson.getInstance().getLabels();
        Map map = new HashMap();
        List list = new ArrayList();
        for(Label l : set){
            if(l.getName().equals("master")){
                continue;
            }
            Map mapt = new HashMap();            
            mapt.put("label_name",l.getName());
            mapt.put("label_description",l.getDescription());
            list.add(mapt);
        }       
        map.put("labels_and_descriptions",list);
        JSONObject json = JSONObject.fromObject(map);
        rsp.setContentType("text/plain;charset=UTF-8");
        rsp.getWriter().println(json.toString());
    }
    
   /**
     * @since 2012/8/15 
     * we add an api that could get all the computer's or nodes' name displayName and description as a json string
     */
    public void doNodesAndLabels(StaplerRequest req, StaplerResponse rsp) throws IOException{     
        List<Node> listt=Hudson.getInstance().getNodes();
        Map map1 = new HashMap();
        List list1 = new ArrayList();
        for(Node n : listt){
            if(n.getDisplayName().equals("master")){
                continue;
            }
            Map mapt1 = new HashMap();            
            mapt1.put("node_name",n.getNodeName());
            mapt1.put("node_label",n.getLabelString());
            list1.add(mapt1);
        }
        map1.put("nodes_and_labels",list1);
        JSONObject json1 = JSONObject.fromObject(map1);
        rsp.setContentType("text/plain;charset=UTF-8");
        rsp.getWriter().println(json1.toString());
    }

    /**
     * Accepts submission from the configuration page.
     */
    public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
        BulkChange bc = new BulkChange(MONITORS_OWNER);
        try {
            Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
            monitors.rebuild(req,req.getSubmittedForm(),getNodeMonitorDescriptors());

            // add in the rest of instances are ignored instances
            for (Descriptor<NodeMonitor> d : NodeMonitor.all())
                if(monitors.get(d)==null) {
                    NodeMonitor i = createDefaultInstance(d, true);
                    if(i!=null)
                        monitors.add(i);
                }
            
            //resolve a bug find in 2012/8/30,this bug is computer set the architecture is repeat many times
            Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " reselve architecture reapet ");
            Set st = new HashSet(monitors.toList());
            monitors.clear();
            try {
                monitors.addAll(st);
            } catch (IOException ex) {
                Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " 处理architecture异常"+ex.getMessage());
            }
            
            rsp.sendRedirect2(".");
        } finally {
            bc.commit();
        }
    }

    /**
     * {@link NodeMonitor}s are persisted in this file.
     */
    private static XmlFile getConfigFile() {
        return new XmlFile(new File(Hudson.getInstance().getRootDir(),"nodeMonitors.xml"));
    }

    public Api getApi() {
        return new Api(this);
    }    
    
    /**
     * Just to force the execution of the static initializer.
     */
    public static void initialize() {}

    private static final Logger LOGGER = Logger.getLogger(ComputerSet.class.getName());

    static {
        try {
            DescribableList<NodeMonitor,Descriptor<NodeMonitor>> r
                    = new DescribableList<NodeMonitor, Descriptor<NodeMonitor>>(Saveable.NOOP);

            // load persisted monitors
            XmlFile xf = getConfigFile();
            if(xf.exists()) {
                DescribableList<NodeMonitor,Descriptor<NodeMonitor>> persisted =
                        (DescribableList<NodeMonitor,Descriptor<NodeMonitor>>) xf.read();
                r.replaceBy(persisted.toList());
            }

            // if we have any new monitors, let's add them
            for (Descriptor<NodeMonitor> d : NodeMonitor.all())
                if(r.get(d)==null) {
                    NodeMonitor i = createDefaultInstance(d,false);
                    if(i!=null)
                        r.add(i);
                }
            monitors.replaceBy(r.toList());
            
            //resolve a bug find in 2012/8/30,this bug is computer set the architecture is repeat many times
            Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " now the NodeMonitor map "+r.size());
            Set st = new HashSet(monitors.toList());
            monitors.clear();
            try {
                monitors.addAll(st);
            } catch (IOException ex) {
                Logger.getLogger(ComputerSet.class.getName()).log(HudsonTestLog.level, " 处理architecture异常"+ex.getMessage());
            }
        
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to instanciate NodeMonitors",e);
        }
    }

    private static NodeMonitor createDefaultInstance(Descriptor<NodeMonitor> d, boolean ignored) {
        try {
            NodeMonitor nm = d.clazz.newInstance();
            nm.setIgnored(ignored);
            return nm;
        } catch (InstantiationException e) {
            LOGGER.log(Level.SEVERE, "Failed to instanciate "+d.clazz,e);
        } catch (IllegalAccessException e) {
            LOGGER.log(Level.SEVERE, "Failed to instanciate "+d.clazz,e);
        }
        return null;
    }
}
